function(run_make_program dir)
  execute_process(
    COMMAND "${RunCMake_MAKE_PROGRAM}" ${ARGN}
    WORKING_DIRECTORY "${dir}"
    OUTPUT_VARIABLE make_program_stdout
    ERROR_VARIABLE make_program_stderr
    RESULT_VARIABLE make_program_result
    )
    if (NOT DEFINED RunMakeProgram_expected_result)
      set(RunMakeProgram_expected_result 0)
    endif()
    if(NOT "${make_program_result}" MATCHES "${RunMakeProgram_expected_result}")
      message(STATUS "
============ beginning of ${RunCMake_MAKE_PROGRAM}'s stdout ============
${make_program_stdout}
=============== end of ${RunCMake_MAKE_PROGRAM}'s stdout ===============
")
    message(STATUS "
============ beginning of ${RunCMake_MAKE_PROGRAM}'s stderr ============
${make_program_stderr}
=============== end of ${RunCMake_MAKE_PROGRAM}'s stderr ===============
")
    message(FATAL_ERROR
            "top ${RunCMake_MAKE_PROGRAM} build failed exited with status ${make_program_result}")
    endif()
  set(make_program_stdout "${make_program_stdout}" PARENT_SCOPE)
endfunction()

function(count_substring STRING SUBSTRING COUNT_VAR)
  string(LENGTH "${STRING}" STRING_LENGTH)
  string(LENGTH "${SUBSTRING}" SUBSTRING_LENGTH)
  if (SUBSTRING_LENGTH EQUAL 0)
    message(FATAL_ERROR "SUBSTRING_LENGTH is 0")
  endif()

  if (STRING_LENGTH EQUAL 0)
      message(FATAL_ERROR "STRING_LENGTH is 0")
  endif()

  if (STRING_LENGTH LESS SUBSTRING_LENGTH)
    message(FATAL_ERROR "STRING_LENGTH is less than SUBSTRING_LENGTH")
  endif()

  set(COUNT 0)
  string(FIND "${STRING}" "${SUBSTRING}" SUBSTRING_START)
  while(SUBSTRING_START GREATER_EQUAL 0)
    math(EXPR COUNT "${COUNT} + 1")
    math(EXPR SUBSTRING_START "${SUBSTRING_START} + ${SUBSTRING_LENGTH}")
    string(SUBSTRING "${STRING}" ${SUBSTRING_START} -1 STRING)
    string(FIND "${STRING}" "${SUBSTRING}" SUBSTRING_START)
  endwhile()

  set(${COUNT_VAR} ${COUNT} PARENT_SCOPE)
endfunction()

function(not_expect make_program_stdout unexpected_output test_name)
  count_substring("${make_program_stdout}" "${unexpected_output}" count)
  if(NOT count EQUAL 0)
    message(STATUS "${test_name}-not_expect - FAILED")
    message(FATAL_ERROR "Expected to find ${unexpected_output} exactly 0 times in ${make_program_stdout} but found ${count} occurrences of ${unexpected_output}")
  else()
    message(STATUS "${test_name}-not_expect - PASSED")
  endif()
endfunction()

function(expect_only_once make_program_stdout expected_output test_name)
  count_substring("${make_program_stdout}" "${expected_output}" count)
  if(NOT count EQUAL 1)
    message(STATUS "${test_name}-expect_only_once - FAILED")
    message(FATAL_ERROR "Expected to find ${expected_output} exactly once in ${make_program_stdout} but found ${count} occurrences of ${expected_output}")
  else()
    message(STATUS "${test_name}-expect_only_once - PASSED")
  endif()
endfunction()

function(expect_n_times string_to_check expected_output expected_count test_name)
  count_substring("${string_to_check}" "${expected_output}" count)
  if(NOT count EQUAL ${expected_count})
    message(STATUS "${test_name}-expect_${expected_count}_times - FAILED")
    message(FATAL_ERROR "Expected to find ${expected_output} exactly ${expected_count} times in ${string_to_check} but found ${count} occurrences of ${expected_output}")
  else()
    message(STATUS "${test_name}-expect_${expected_count}_times - PASSED")
  endif()
endfunction()

function(autogen_executable_test exe)
  if (QtCore_VERSION VERSION_GREATER_EQUAL 5.15.0)
    if(RunCMake_GENERATOR MATCHES "Ninja Multi-Config")
      block()
        set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMake-configure")
        set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig-multi-config-build)
        run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
        unset(RunCMake_TEST_VARIANT_DESCRIPTION)
        set(RunCMake_TEST_NO_CLEAN 1)
        foreach(config IN ITEMS Debug Release RelWithDebInfo)
          block()
            set(RunCMake_TEST_EXPECT_stdout ".*running_exe_${config}*")
            set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}-expect_running_exe_${config}")
            run_cmake_command(Auto${exe}ExecutableConfig-multi-config-build ${CMAKE_COMMAND} --build . --config ${config})
          endblock()
        endforeach()
        set(RunCMake_TEST_EXPECT_stdout "ninja: no work to do")
        foreach(config IN ITEMS Debug Release RelWithDebInfo)
          block()
          set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}-expect_no_work_to_do")
            run_cmake_command(Auto${exe}ExecutableConfig-multi-config-build ${CMAKE_COMMAND} --build . --config ${config})
          endblock()
        endforeach()
      endblock()
      block()
        set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig-build)
        run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
        foreach(config IN ITEMS Debug Release RelWithDebInfo)
          block()
            run_make_program(${RunCMake_TEST_BINARY_DIR} --verbose -f build-${config}.ninja)

            set(expected_output "running_exe_${config}")
            expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig-${config}-${expected_output}")

            foreach(sub_config IN ITEMS Debug Release RelWithDebInfo)
              if(NOT sub_config STREQUAL config)
                set(unexpected_output "running_exe_${sub_config}")
                not_expect("${make_program_stdout}" "${unexpected_output}" "Auto${exe}ExecutableConfig-${config}-${unexpected_output}")
              endif()
            endforeach()

            if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
              set(expected_output "cmake_autogen")
            else()
              set(expected_output "cmake_autorcc")
            endif()
            expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig-${config}-${expected_output}")
          endblock()
        endforeach()
      endblock()
      block()
        foreach(ninja_config IN ITEMS Debug Release RelWithDebInfo)
          foreach(target_config IN ITEMS Debug Release RelWithDebInfo)
            block()
              set(TEST_SUFFIX "-CrossConfig-${ninja_config}-${target_config}")
              set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig${TEST_SUFFIX}-build)
              set(RunCMake_TEST_VARIANT_DESCRIPTION ${TEST_SUFFIX})
              run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_CROSS_CONFIGS=all -DCMAKE_DEFAULT_BUILD_TYPE=${ninja_config} -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
              unset(RunCMake_TEST_VARIANT_DESCRIPTION)

              run_make_program(${RunCMake_TEST_BINARY_DIR} --verbose -f build-${ninja_config}.ninja dummy:${target_config})

              set(expected_output "running_exe_${ninja_config}")
              expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig${TEST_SUFFIX}-${expected_output}")

              foreach(sub_config IN ITEMS Debug Release RelWithDebInfo)
                if(NOT sub_config STREQUAL ninja_config)
                  set(unexpected_output "running_exe_${sub_config}")
                  not_expect("${make_program_stdout}" "${unexpected_output}" "Auto${exe}ExecutableConfig${TEST_SUFFIX}-${unexpected_output}")
                endif()
              endforeach()

              if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
                set(expected_output "cmake_autogen")
              else()
                set(expected_output "cmake_autorcc")
              endif()
              expect_only_once("${make_program_stdout}" "${expected_output}" "Auto${exe}ExecutableConfig${TEST_SUFFIX}-${expected_output}")
            endblock()
          endforeach()
        endforeach()
      endblock()
      block()
        foreach(ninja_config IN ITEMS Debug Release RelWithDebInfo)
          set(TEST_SUFFIX "-CrossConfig-${ninja_config}-all-all")
          set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig${TEST_SUFFIX}-build)
          set(RunCMake_TEST_VARIANT_DESCRIPTION ${TEST_SUFFIX})
          run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_CROSS_CONFIGS=all -DCMAKE_AUTOGEN_BETTER_GRAPH_MULTI_CONFIG=ON)
          unset(RunCMake_TEST_VARIANT_DESCRIPTION)
          run_make_program(${RunCMake_TEST_BINARY_DIR} --verbose -f build-${ninja_config}.ninja all:all)
        endforeach()
      endblock()
    elseif (RunCMake_GENERATOR MATCHES "Ninja|Make")
      block()
        set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/Auto${exe}ExecutableConfig-build)
        foreach(config IN ITEMS Debug Release RelWithDebInfo)
          block()
            set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}")
            run_cmake_with_options(Auto${exe}ExecutableConfig ${RunCMake_TEST_OPTIONS} -DCMAKE_BUILD_TYPE=${config} -DCMAKE_AUTOGEN_VERBOSE=ON)
            unset(RunCMake_TEST_VARIANT_DESCRIPTION)
            set(RunCMake_TEST_NO_CLEAN 1)
            set(RunCMake_TEST_EXPECT_stdout ".*running_exe_${config}*")
            run_cmake_command(Auto${exe}ExecutableConfig-${config}-build ${CMAKE_COMMAND} --build .)
          endblock()
        endforeach()
      endblock()
    endif()
  endif()

  # Visual Studio specific dependency tests
  if (RunCMake_GENERATOR MATCHES "Visual Studio")
    block()
      set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${exe}Example-build)
      set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMake-configure")
      run_cmake_with_options(${exe}Example ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON)
      unset(RunCMake_TEST_VARIANT_DESCRIPTION)
      set(RunCMake_TEST_NO_CLEAN 1)
      foreach(config IN ITEMS Debug Release RelWithDebInfo)
        block()
          set(RunCMake_TEST_VARIANT_DESCRIPTION "-${config}-first-build")
          run_cmake_command(${exe}Example-build ${CMAKE_COMMAND} --build . --config ${config})
        endblock()
      endforeach()
      foreach(config IN ITEMS Debug Release RelWithDebInfo)
        block()
          if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
            set(RunCMake_TEST_NOT_EXPECT_stdout "Auto${exe}")
            set(not_expect_description "Auto${exe}")
          else ()
            set(RunCMake_TEST_NOT_EXPECT_stdout "Auto${exe}")
            set(not_expect_description "Auto${exe}")
          endif()
          set(RunCMake_TEST_VARIANT_DESCRIPTION "-second-build-${config}_expect_no_${not_expect_description}")
          run_cmake_command(${exe}Example-build ${CMAKE_COMMAND} --build . --config ${config})
        endblock()
      endforeach()
    endblock()
  endif()

  if (RunCMake_GENERATOR MATCHES "Xcode")
    block()
      set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${exe}Example-build)
      set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMake-configure")
      set(RunCMake_TEST_EXPECT_stderr ".*")
      run_cmake_with_options(${exe}Example ${RunCMake_TEST_OPTIONS} -DCMAKE_AUTOGEN_VERBOSE=ON)
      set(RunCMake_TEST_NO_CLEAN 1)
      set(RunCMake_MAKE_PROGRAM ${CMAKE_COMMAND})
      run_make_program(${RunCMake_TEST_BINARY_DIR}  --build . --config Debug)
      if (exe STREQUAL "Moc")
        set(expected_count 4)
      elseif (exe STREQUAL "Uic")
        set(expected_count 1)
      else()
        set(expected_count 12)
      endif()
      expect_n_times("${make_program_stdout}" "Auto${exe}:" ${expected_count} "${exe}Example-build-Auto${exe}")

      if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
        expect_n_times("${make_program_stdout}" "AutoGen:" 5 "${exe}Example-build-AutoGen:")
      endif()


      foreach(config IN ITEMS Release RelWithDebInfo)
        block()
          # Note: We expect to see Auto${exe} or AutoGen in the output for
          # moc and uic because they should be triggered per configuration.
          # For rcc, we don't expect to see Auto${exe} or AutoGen in the output
          # because all configurations trigger with the first configuration.
          run_make_program(${RunCMake_TEST_BINARY_DIR} --build . --config ${config})
          if (exe STREQUAL "Moc" OR exe STREQUAL "Uic")
            expect_n_times("${make_program_stdout}" "Auto${exe}:" ${expected_count} "${exe}Example-build-${config}-Auto${exe}")
            expect_n_times("${make_program_stdout}" "AutoGen" 5 "${exe}Example-build-${config}-AutoGen:")
          else()
            not_expect("${make_program_stdout}" "Auto${exe}" "${exe}Example-build-${config}_Auto${exe}")
            not_expect("${make_program_stdout}" "AutoGen" "${exe}Example-build-${config}_AutoGen")
          endif()
        endblock()
      endforeach()
    endblock()
  endif()
endfunction()
